#include <algorithm>
#include <memory>
#include "graph.h"

void swap(vertex_id & lhs, vertex_id & rhs)
{
  lhs.swap(rhs);
}

size_t hash_value(vertex_id v)
{
  return boost::hash_value(v.vertex_index) * 4409
      + boost::hash_value(v.bucket_index);
}

bool operator ==(vertex_id const & lhs, vertex_id const & rhs)
{
  return lhs.vertex_index == rhs.vertex_index && lhs.bucket_index
      == rhs.bucket_index;
}

bool operator !=(vertex_id const & lhs, vertex_id const & rhs)
{
  return !(lhs == rhs);
}

db_graph_t::db_graph_t(std::string const & buckets_path) :
  bucket_manager_(buckets_path, 10)
{
}

adj_vertices_range_t db_graph_t::adjacent_range(vertex_id v) const
{
  details::bucket_t const & bucket = bucket_manager_.get_bucket(v.bucket_index);
  return bucket.adjacent_range(v.vertex_index);
}

geodata_t db_graph_t::get_data(vertex_id v) const
{
  //  data_map_t::const_iterator it = data_map_.find(v);
  //  if (it == data_map_.end())
  //    return geodata_t(0, 0);
  //  else
  //    return it->second;
  details::bucket_t const & bucket = bucket_manager_.get_bucket(v.bucket_index);
  return bucket.get_data(v.vertex_index);
}

namespace details
{
  bucket_t::bucket_t(bucket_index_t bucket_id, void * raw_bucket) :
    bucket_id_(bucket_id),
        raw_bucket_(static_cast<volatile raw_bucket_t *> (raw_bucket))
  {
  }

  geodata_t bucket_t::get_data(vertex_index_t v) const
  {
    if (v >= raw_bucket_->vertices_count)
    {
      return geodata_t();
    }

    volatile raw_vertex_t * raw_v =
        reinterpret_cast<volatile raw_vertex_t *> (&raw_bucket_->data[v_rawsize
            * v]);
    return geodata_t(raw_v->lat, raw_v->lon);
  }

  adj_vertices_range_t bucket_t::adjacent_range(vertex_index_t v) const
  {
    volatile adj_vertex * begin = 0;
    volatile adj_vertex * end = 0;
    if (v < raw_bucket_->vertices_count)
    {
      volatile raw_vertex_t
          * raw_v =
              reinterpret_cast<volatile raw_vertex_t *> (&raw_bucket_->data[v_rawsize
                  * v]);
      volatile raw_vertex_t
          * next_v =
              reinterpret_cast<volatile raw_vertex_t *> (&raw_bucket_->data[v_rawsize
                  * (v + 1)]);
      uint32_t edges_size = next_v->edges_offset - raw_v->edges_offset;

      size_t edges_offset = (raw_bucket_->vertices_count + 1) * v_rawsize;
      begin
          = reinterpret_cast<volatile adj_vertex *> (&raw_bucket_->data[edges_offset
              + raw_v->edges_offset * e_rawsize]);
      end = begin + edges_size;
    }

    assert (begin->vertex_index.bucket_index < 100);
    assert (end->vertex_index.bucket_index < 100);
    return adj_vertices_range_t(begin, end);
  }

  bucket_t::~bucket_t()
  {
    free(((void *) raw_bucket_));
  }

  bucket_manager_t::bucket_manager_t(std::string const & path_to_buckets,
                                     size_t buckets_count) :
    bucket_loader_(path_to_buckets), max_buckets_(buckets_count)
  {
  }

  bucket_t const & bucket_manager_t::get_bucket(bucket_index_t bucket_id)
  {
    bucket_index_storage_t::iterator it = bucket_index_.find(bucket_id);
    if (it != bucket_index_.end())
    {
      bucket_storage_t::iterator b_it = it->second;
      std::iter_swap(b_it, buckets_.begin());
      bucket_index_[bucket_id] = buckets_.begin();
      bucket_index_[(*b_it)->bucket_id()] = b_it;

      return *buckets_.front();
    }
    else
    {
      return *load_bucket(bucket_id);
    }
  }

  bucket_t * bucket_manager_t::load_bucket(bucket_index_t bucket_id)
  {
    if (buckets_.size() == max_buckets_)
      drop_bucket();

    bucket_t * new_bucket = new bucket_t(bucket_id,
                                         bucket_loader_.load_bucket(bucket_id));
    buckets_.push_front(new_bucket);
    bucket_index_.insert(std::make_pair(bucket_id, buckets_.begin()));

    return new_bucket;
  }

  void bucket_manager_t::drop_bucket()
  {
    if (buckets_.empty())
      return;

    bucket_index_.erase(buckets_.back()->bucket_id());
    buckets_.pop_back();
  }
}

